﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace PayloadTracker.Tiles
{
    public class OSMProvider : ITileProvider
    {
        public class OSMKey : ITileProvider.TileKey
        {
            public int x, y, zoom;
            public override string GetHash()
            {
                return string.Format("{0}:{1}:{2}", x, y, zoom);
            }
        }

        string m_ID, m_Extension;
        Uri m_URL;


        private OSMProvider()
        {

        }
        public OSMProvider(string ID, Uri URL, string extension)
        {
            m_ID = ID;
            m_URL = URL;
            m_Extension = extension;
        }

        #region ITileProvider Members

        public override string Name()
        {
            return m_ID;
        }
        public override List<ITileProvider.TileKey> GetUniqueKeysForRegion(Coordinate UL, Coordinate LR, int Zoom)
        {
            List<ITileProvider.TileKey> ret = new List<TileKey>();

            var ulp = Coordinate2Tile(UL, Zoom);
            var lrp = Coordinate2Tile(LR, Zoom);

            for (int x = Math.Max(0, ulp.X - 1); x <= lrp.X; x++)
                for (int y = Math.Max(0, ulp.Y - 1); y <= lrp.Y; y++)
                {

                    OSMKey key = new OSMKey();
                    key.x = x;
                    key.y = y;
                    key.zoom = Zoom;

                    ret.Add(key);

                }

            return ret;
        }

        public override Tile GetTileFromKey(ITileProvider.TileKey key, bool Async)
        {
            Tile tile;
            OSMKey ok = key as OSMKey;

            string tilepath = Path.Combine(GetCachePath(), Path.Combine(ok.zoom.ToString(), ok.x.ToString()));
            string tilefile = Path.Combine(tilepath, ok.y.ToString() + "." + m_Extension);

            if (!Directory.Exists(Path.Combine(GetCachePath(), ok.zoom.ToString())))
                Directory.CreateDirectory(Path.Combine(GetCachePath(), ok.zoom.ToString()));

            if (!Directory.Exists(tilepath))
                Directory.CreateDirectory(tilepath);

            var bb = Tile2BoundingBox(ok.x, ok.y, ok.zoom);

            if (File.Exists(tilefile) && new FileInfo(tilefile).Length > 0)
                tile = new Tile(System.Drawing.Image.FromFile(tilefile), bb.UL, bb.LR);
            else 
            {
                var url = new System.Uri(m_URL + "/" + ok.zoom.ToString() + "/" + ok.x.ToString() + "/" + ok.y.ToString() + "." + m_Extension);
                tile = new Tile(url, tilefile, bb.UL, bb.LR, Async);
            }
            
            return tile;
        }

        public static System.Drawing.Point Coordinate2Tile(Coordinate c, int Zoom)
        {
            return new System.Drawing.Point(Long2Tile(c.Long, Zoom), Lat2Tile(c.Lat, Zoom));
        }
        public static int Long2Tile(double Long, int Zoom)
        {
            return ((int)Math.Floor((Long + 180.0f) / 360.0 * Math.Pow(2.0, Zoom)));
        }
        public static int Lat2Tile(double Lat, int Zoom)
        {
            return (int)(Math.Floor((1.0 - Math.Log(Math.Tan(Lat * Math.PI / 180.0) + 1.0 / Math.Cos(Lat * Math.PI / 180.0)) / Math.PI) / 2.0 * Math.Pow(2.0, Zoom)));
        }

        public static double Tile2Long(int x, int Zoom)
        {
            return x / Math.Pow(2.0, Zoom) * 360.0 - 180;
        }

        public static double Tile2Lat(int y, int Zoom)
        {
            double n = Math.PI - 2.0 * Math.PI * y / Math.Pow(2.0, Zoom);
            return 180.0 / Math.PI * Math.Atan(0.5 * (Math.Exp(n) - Math.Exp(-n)));
        }
        //public static double Tile2Long(Tile t)
        //{
        //    return Tile2Lat(t.TileX, t.Zoom);
        //}
        //public static double Tile2Lat(Tile t)
        //{
        //    return Tile2Lat(t.TileY, t.Zoom);
        //}
        //public static BoundingBox Tile2BoundingBox(Tile t)
        //{
        //    return Tile2BoundingBox(t.TileX, t.TileY, t.Zoom);
        //}
        public static BoundingBox Tile2BoundingBox(System.Drawing.Point p, int Zoom)
        {
            return Tile2BoundingBox(p.X, p.Y, Zoom);
        }
        public static BoundingBox Tile2BoundingBox(int x, int y, int Zoom)
        {
            return new BoundingBox(Tile2Lat(y, Zoom), Tile2Long(x + 1, Zoom), Tile2Lat(y + 1, Zoom), Tile2Long(x, Zoom));
        }

        #endregion

        public override void ReadXml(System.Xml.XmlReader reader)
        {
            while (reader.MoveToNextAttribute())
            {
                switch (reader.Name.ToLower())
                {
                    case "id":
                        {
                            m_ID = reader.Value;
                            break;
                        }
                    case "extension":
                        {
                            m_Extension = reader.Value;
                            break;
                        }
                    case "enabled":
                        {
                            Enabled = reader.ReadContentAsBoolean();
                            break;
                        }
                    default:
                        {
                            Logger.LOG.Error("Unknown attribute specified in OSMProvider definition.");
                            break;
                        }
                }
            }

            reader.Read();

            string uristr = reader.ReadContentAsString();
            m_URL = new Uri(uristr);

            reader.Read();
            
        }

        public override void WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteAttributeString("ID", m_ID);
            writer.WriteAttributeString("Extension", m_Extension);
            writer.WriteAttributeString("Enabled", Enabled.ToString());
            writer.WriteString(m_URL.ToString());
        }

    }
}
